import parole, pygame
import random, logging
from util import *
shader = parole.shader
from parole.colornames import colors
import interface, dungeon, sim, town, highscore, main, sim_items, sim_creatures

#========================================

cmdPoll = None
schedule = None
player = None
transient = None

#========================================

class CommandPoll(sim.Listener):
    awaitCommand = False

    def listen(self, event):
        if event.id == 'get command':
            # check for caps lock warning
            if pygame.key.get_mods() & pygame.KMOD_CAPS:
                transient('Warning: Your caps lock key is active!')
                parole.warn('Your caps lock key is active!')

            self.awaitCommand = True
            parole.debug('\n\n\nAwaiting command [time=%d]...', main.schedule.time)

#========================================

def init(scheduleObj, playerObj):
    global cmdPoll, schedule, player, transient
    cmdPoll = CommandPoll()
    schedule = scheduleObj
    schedule.listen('get command', cmdPoll)
    player = playerObj
    transient = main.transient

#========================================

def handleCommand(cmd):
    if not cmdPoll.awaitCommand:
        parole.error('*** Got a command while not waiting for one!')
        return

    main.clearTransientAnnotations()

    cmdFuncName = "do_%s" % (cmd.replace(' ', '_'),)
    try:
        cmdFunc = globals()[cmdFuncName]
    except KeyError:
        parole.warn('Unhandled command: %r', cmd)
        return

    # cmdFunc returns True if we should stop waiting for commands and advance
    # time
    if cmdFunc(cmd):
        cmdPoll.awaitCommand = False

#========================================
# Command implementations follow
#========================================

def do_quit(cmd):
    """
    Command "quit". Asks the player to confirm, and if yes, causes death by
    suicide. If parole.conf.options.wizard is True, exits the program
    immediately.
    """
    if parole.conf.options.wizard:
        raise parole.ParoleShutdown
    def __onChoose(x):
        if x == "Yes":
            schedule.add(sim.Event('set stat', schedule.time,
                (player, 'hp', -666), dests=[player]))
            player.lastDamageSource = main.Suicide()
            schedule.add(sim.Event('get command', schedule.time+1))
            cmdPoll.awaitCommand = False

    interface.ObjectChooser(["No", "Yes"], __onChoose, 
            title="{\Gold Commit Suicide?}", choosableType=None).push()

#========================================

def do_view(cmd):
    """
    Commands "view {left,right,down,up}". Scroll the active MapFrame
    (main.mapFrame) around one tile at a time.

    Does not advance time.
    """
    mapFrame = main.mapFrame
    if cmd == "view left":
        mapFrame.scrollTiles(-1,0)
    elif cmd == "view right":
        mapFrame.scrollTiles(1,0)
    elif cmd == "view down":
        mapFrame.scrollTiles(0,1)
    elif cmd == "view up":
        mapFrame.scrollTiles(0,-1)

do_view_left = do_view
do_view_right = do_view
do_view_down = do_view
do_view_up = do_view

#========================================

def do_examine(cmd):
    """
    Command "examine". Activates an instance of interface.ExamineInterface for
    examining tiles on the map.

    Does not advance time.
    """
    interface.ExamineInterface().push()

#========================================

def do_highlight(cmd):
    """
    Command "highlight". Annotates all currently visible tiles containing an
    item, creature, or stairs with "You see <blah>".

    Does not advance time.
    """
    # highlight all currently visible items/creatures/stairs
    mapFrame = main.mapFrame
    map = mapFrame.getMap()
    if not mapFrame.fovObj:
        searchTiles = map
    else:
        searchTiles = [map[col,row] for (col,row) in mapFrame.visibleTiles]

    for t in searchTiles:
        things = []
        for obj in player.visibleObjectsAt(t):
            if isinstance(obj, sim_items.Item) or isinstance(obj, sim_creatures.Creature) \
                    and obj is not player:
                things.append(obj)
        if things:
            main.transient('You see: %s.' % ', '.join([x.noun() for x in \
                things]), t)

#========================================

def do_clear(cmd):
    """
    Command "clear". Dummy command handler to prevent warnings about unhandle
    command; active inerfaces will handle this command on their own.

    Does not advance time.
    """
    pass

#========================================

def do_walk(cmd):
    """
    Command "walk
    {north,south,east,west,northwest,northeast,southwest,southeast}". Causes the
    player to attempt to move in the given direction by scheduling a "walk"
    event.

    Advances time.
    """
    schedule.sendNow(sim.Event('walk', schedule.time,
            args=cmd.split()[1:], dests=[player]))
    return True

do_walk_north = do_walk
do_walk_south = do_walk
do_walk_east = do_walk
do_walk_west = do_walk
do_walk_northeast = do_walk
do_walk_southeast = do_walk
do_walk_northwest = do_walk
do_walk_southwest = do_walk

#========================================

def do_openclose(cmd):
    """
    Commands "{open,close}". Attempts to toggle the open/closed status of a
    player-adjacent door by calling Player.toggleDoor(); if multiple doors are
    present, asks for a direction.

    Advances time iff successful.
    """
    mapFrame = main.mapFrame
    map = mapFrame.getMap()
    def __tryToggleDoor(thedoor):
        # once the intended door is known, this tries opening it and
        # scheduling the next get-command
        evtime = player.toggleDoor(thedoor)
        if evtime < 0:
            # nothing happened; get another command
            schedule.add(sim.Event('get command', schedule.time))
        else:
            schedule.add(sim.Event('get command', evtime+1))

    def __handleDoorDir(disp):
        main.clearTransientAnnotations()
        dx, dy = disp
        doorPos = player.parentTile.col + dx, player.parentTile.row + dy
        if doorPos[0] < 0 or doorPos[0] >= map.cols \
            or doorPos[1] < 0 or doorPos[1] >= map.rows:
                return
        doorTile = map[doorPos]
        for obj in doorTile:
            if isinstance(obj, sim.Door):
                __tryToggleDoor(obj)
                cmdPoll.awaitCommand = False
                return
        main.transient("There is no door there.", doorTile)

    def __handleDoorDirCancel():
        main.clearTransientAnnotations()

    door = None
    for t in adjacentTiles(player.parentTile):
        for obj in t:
            if isinstance(obj, sim.Door):
                if not door:
                    door = obj
                else:
                    # there are multiple doors
                    main.transient("Which direction?")
                    interface.DirectionChooser(__handleDoorDir,
                            __handleDoorDirCancel).push()
                    return False
                    
    if door:
        __tryToggleDoor(door)
        return True
    else:
        main.transient("There is no door nearby.")
        return False

do_open = do_openclose
do_close = do_openclose

#========================================

def do_get(cmd):
    """
    Command "get". Attempts to cause the player to pick up an item by calling
    Player.pickupItem(). Fails if no items are present at player's current
    location. If multiple items are present, asks the player to select which one
    to pick up.

    Advances time iff successful.
    """
    def __tryPickup(item):
        evtime = player.pickupItem(item)
        if evtime < 0:
            # nothing happened; get another command
            schedule.add(sim.Event('get command', schedule.time))
        else:
            schedule.add(sim.Event('get command', evtime + 1))

    def __handleChooseGet(item):
        cmdPoll.awaitCommand = False
        __tryPickup(item)
        return

    # find an item in the player's tile
    target = None
    for obj in player.parentTile:
        if isinstance(obj, sim_items.Item):
            if target:
                # there are multiple possible items
                interface.ObjectChooser(
                        [o for o in player.parentTile if isinstance(o,
                            sim_items.Item)],
                        __handleChooseGet,
                        title="{\Gold Pick up which item?}").push()
                return False
            else:
                target = obj
    if target:
        __tryPickup(target)
        return True
    else:
        transient("There is nothing here that can be picked up.")
        return False

#========================================

def do_drop(cmd):
    """
    Command "drop". Attempts to cause the player to drop an item by calling
    Player.dropItem(). Fails if no items are present in player's inventory.
    If multiple items are present, asks the player to select which one
    to drop.

    Advances time iff successful.
    """
    def __doDrop(target):
        evtime = player.dropItem(target)
        if evtime < 0:
            # nothing happened; get another command
            schedule.add(sim.Event('get command', schedule.time))
        else:
            schedule.add(sim.Event('get command', evtime + 1))

    def __handleChooseDrop(target):
        cmdPoll.awaitCommand = False
        __doDrop(target)

    if not player.inventory:
        transient("You have nothing to drop.")
        return False
    if len(player.inventory) == 1:
        target = player.inventory[0]
        __doDrop(target)
        return True
    else:
        # choose what to drop
        interface.ObjectChooser(interface.inventoryView(player.inventory),
                __handleChooseDrop, title="{\Gold Drop which item?}").push()
        return False

#========================================

def do_inventory(cmd):
    """
    Command "inventory". Displays an ObjectChooser interface allowing the player
    to view currently carried items, and select items for detailed descriptions.

    Does not advance time.
    """
    if not player.inventory:
        transient("You are unburdened by the sin of possession.")
        return

    def __describeItem(target):
        desc = '{\Gold %s:}\n\n%s' % (target.noun(capitalized=1),
                target.description())
        interface.ModalBox(desc, main.arial, lambda: 0).push()

    interface.ObjectChooser(interface.inventoryView(player.inventory),
            __describeItem, 
        title="{\Gold You carry the following, for a total weight of %s lbs "
        "(capacity %s lbs). Select an item for its detailed description.}" \
                % (player.inventoryWeight(),
            player.inventoryCapacity())).push()

#========================================

def do_wear(cmd):
    """
    Command "wear". Attempts to cause the player to wear an item by calling
    player.wear(). Fails if no unworn wearable items are present in player's
    inventory. If multiple unworn wearable items are present, asks the player to
    select which one to wear. Note that there is no distinction between
    "wearing" and "wielding".

    Advances time iff successful.
    """
    targets = []
    for obj in player.inventory:
        if isinstance(obj, sim_items.Wearable) and obj not in \
                player.wearSlots.values():
            targets.append(obj)

    if not targets:
        transient("You have nothing that could be worn or wielded.")
        return False

    def __handleChooseWearable(target):
        cmdPoll.awaitCommand = False
        evtime = player.wear(target)
        if evtime < 0:
            # nothing happened; get another command
            schedule.add(sim.Event('get command', schedule.time))
        else:
            schedule.add(sim.Event('get command', evtime + 1))
        

    interface.ObjectChooser(targets, __handleChooseWearable, 
        title="{\Gold Wear or wield what?}").push()
    return False

#========================================

def do_remove(cmd):
    """
    Command "remove". Attempts to cause the player to remove an item by calling
    player.remove(). Fails if no worn items are present in player's inventory.
    If multiple worn items are present, asks the player to select which one to
    remove. Note that there is no distinction between "removing" and
    "unwielding".

    Advances time iff successful.
    """
    targets = []
    for obj in player.wearSlots.values():
        if obj:
            targets.append(obj)

    if not targets:
        transient("You've nothing to remove or unwield.")
        return False

    def __handleChooseWearable(target):
        cmdPoll.awaitCommand = False
        evtime = player.remove(target)
        if evtime < 0:
            # nothing happened; get another command
            schedule.add(sim.Event('get command', schedule.time))
        else:
            schedule.add(sim.Event('get command', evtime + 1))

    interface.ObjectChooser(targets, __handleChooseWearable, 
        title="{\Gold Remove or unwield what?}").push()
    return False

#========================================

def do_equipment(cmd):
    """
    Command "equipment". Displays an ObjectChooser interface allowing the player
    to view currently worn/wielded items, and select items for detailed
    descriptions.

    Does not advance time.
    """
    equip = []
    for obj in player.wearSlots.values():
        if obj:
            equip.append(obj)
    for obj in player.inventory:
        if isinstance(obj, sim_items.LightSource) and obj.on:
            equip.append(obj)
    if not equip:
        transient("You've nothing worn, wielded, or lit.")
        return

    def __describeItem(target):
        desc = '{\Gold %s:}\n\n%s' % (target.noun(capitalized=1),
                target.description())
        interface.ModalBox(desc, main.arial, lambda: 0).push()

    interface.ObjectChooser(interface.inventoryView(equip), __describeItem, 
        title="{\Gold You wear and wield the following. "
              "Select an item for its detailed description.}" ).push()

#========================================

def do_wait(cmd):
    """
    Command "wait". Causes the player to do nothing and allow an amount of time
    equal to main.waitTime to pass in the simulation.

    Advances time.
    """
    schedule.add(sim.Event('get command', schedule.time+main.waitTime))
    player.timeLastAction = main.waitTime
    return True

#========================================

def do_light(cmd):
    """
    Command "light". Attempts to cause the player to light a light source by
    calling player.light(). Fails if no unlit light sources are present in
    player's inventory. If multiple unlit light sources are present, asks the
    player to select which one to light.

    Advances time iff successful.
    """
    lights = []
    existingSource = None
    for obj in player.inventory:
        if isinstance(obj, sim_items.LightSource):
            if obj.on:
                existingSource = obj
            elif obj.burnTime > 0:
                # we can light carried lightsources 
                lights.append(obj)
        elif isinstance(obj, sim_items.Scroll):
            lights.append(obj)
    for obj in player.parentTile:
        if isinstance(obj, sim_items.LightSource) and (not obj.on) and \
                obj.burnTime > 0:
            # we can light lightsources on the ground
            lights.append(obj)
        elif isinstance(obj, sim_items.Scroll):
            lights.append(obj)

    if not lights:
        transient("There is nothing nearby to light.")
        return

    def __handleChooseLight(target):
        cmdPoll.awaitCommand = False
        evtime = player.light(target)
        if evtime < 0:
            # nothing happened; get another command
            schedule.add(sim.Event('get command', schedule.time))
        else:
            schedule.add(sim.Event('get command', evtime + 1))
        

    interface.ObjectChooser(lights, __handleChooseLight, 
            title="{\Gold Choose an item to light with your %s:}" %\
                    (existingSource and existingSource.noun(article=0) \
                     or 'flint',)).push()
    return False

#========================================

def do_snuff(cmd):
    """
    Command "snuff". Attempts to cause the player to snuff a light source by
    calling player.snuff(). Fails if no lit light sources are present in player's
    inventory. If multiple lit light sources are present, asks the player to
    select which one to snuff.

    Advances time iff successful.
    """
    #FIXME: code duplication re light
    lights = []
    existingSource = None
    for obj in player.inventory:
        if isinstance(obj, sim_items.LightSource):
            if obj.on:
                lights.append(obj)
    for obj in player.parentTile:
        if isinstance(obj, sim_items.LightSource) and obj.on:
            # we can light lightsources on the ground
            lights.append(obj)

    if not lights:
        transient("There is no lightsource nearby to snuff.")
        return

    def __handleChooseSnuff(target):
        cmdPoll.awaitCommand = False
        evtime = player.snuff(target)
        if evtime < 0:
            # nothing happened; get another command
            schedule.add(sim.Event('get command', schedule.time))
        else:
            schedule.add(sim.Event('get command', evtime + 1))
        

    interface.ObjectChooser(lights, __handleChooseSnuff, 
            title="{\Gold Choose a light to snuff:}").push()
    return False

#========================================

def do_descend(cmd):
    """
    Command "descend". Causes the player to attempt to go down a
    staircase/mineshaft at his current location. Asks the player whether to
    descend carefuly or recklessly.

    Advances time iff successful.
    """
    thisLevel = main.currentLevel
    thisMap, thisStartPos, thisDownPos = main.levels[main.currentLevel]
    if player.pos != thisDownPos:
        transient('There is no way down here.')
        return False

    # ask whether to climb or jump. climbing takes a long time, while
    # jumping is quick but dangerous
    climb = "Climb down carefully (taking about %.1f seconds)" %\
            (player.timeOfAction(player.climbEnergy)/1000.,)
    jump = "Jump down recklessly (taking about %.1f seconds)" %\
            (player.timeOfAction(player.jumpEnergy)/1000.,)

    def __onChoose(choice):
        cmdPoll.awaitCommand = False
        if main.currentLevel == 'town':
            nextLevel = 1
        else:
            nextLevel = main.currentLevel + 1
        main.fadeMusicOut(1000)
        if nextLevel not in main.levels:
            thelevel, ustairPos, dstairPos = dungeon.makeLevel(nextLevel)
            main.addLevel(nextLevel, thelevel, ustairPos, dstairPos)
            main.originalAmbientIntensity[thelevel] = thelevel.ambientIntensity
        nextMap, startPos, dstairPos = main.levels[nextLevel]
        schedule.listen('new map', main.MapSender(nextMap, startPos))

        if choice == climb:
            transient('You start climbing down the shaft...')
            timeOfClimb = player.timeOfAction(player.climbEnergy)
            player.doFatigue(player.climbEnergy)
            schedule.add(sim.Event('new map', schedule.time + timeOfClimb, 
                "You climb down from the shaft."))
        elif choice == jump:
            timeOfJump = player.timeOfAction(player.jumpEnergy)
            player.doFatigue(player.jumpEnergy)
            schedule.add(sim.Event('new map', schedule.time + timeOfJump,
                "You tumble down out of the shaft. Ouch!"))
            # TODO: tie falling damage to carried weight
            schedule.add(sim.Event('decrease stat', 
                schedule.time + timeOfJump,
                (player, 'hp', random.randint(5,15)), dests=[player]))
            player.lastDamageSource = main.FallDamage()

    interface.ObjectChooser([climb, jump], __onChoose, 
            title="{\Gold How would you like to descend the mine shaft?}",
            choosableType=None).push()
    return False

#========================================

def do_ascend(cmd):
    """
    Command "ascend". Causes the player to attempt to go up a
    staircase/mineshaft at his current location. 

    Advances time iff successful.
    """
    # FIXME: code duplication re descend
    thisLevel = main.currentLevel
    thisMap, thisUpPos, thisDownPos = main.levels[main.currentLevel]
    if player.pos != thisUpPos:
        transient('There is no way up here.')
        return False

    if main.currentLevel == 'town':
        transient('There is no way up here.')
        return False
    elif main.currentLevel == 1:
        nextLevel = 'town'
    else:
        nextLevel = main.currentLevel - 1

    main.fadeMusicOut(1000)
    if nextLevel not in main.levels:
        thelevel, ustairPos, dstairPos = dungeon.makeLevel(nextLevel)
        main.addLevel(nextLevel, thelevel, ustairPos, dstairPos)
        main.originalAmbientIntensity[thelevel] = thelevel.ambientIntensity

    if nextLevel == 'town':
        nextMap, xxx, dstairPos = main.levels[nextLevel]
    else:
        nextMap, startPos, dstairPos = main.levels[nextLevel]

    schedule.listen('new map', main.MapSender(nextMap, dstairPos))
    transient('You start climbing up the shaft...')
    climbTime = player.timeOfAction(player.climbEnergy)
    player.doFatigue(player.climbEnergy)
    schedule.add(sim.Event('new map', schedule.time + \
            climbTime, "You climb up out of the shaft."))
    return True

#========================================

def do_quaff(cmd):
    """
    Command "quaff". Attempts to cause the player to quaff a potion by calling
    player.quaff(). Fails if no potions are present in player's inventory. If
    multiple potions are present, asks the player to select which one to quaff.

    Advances time iff successful.
    """
    targets = []
    for obj in player.inventory:
        if isinstance(obj, sim_items.Potion):
            targets.append(obj)

    if not targets:
        transient("You have no potions.")
        return False

    def __handleChoosePotion(target):
        cmdPoll.awaitCommand = False
        evtime = player.quaff(target)
        if evtime < 0:
            # nothing happened; get another command
            schedule.add(sim.Event('get command', schedule.time))
        else:
            schedule.add(sim.Event('get command', evtime + 1))

    interface.ObjectChooser(targets, __handleChoosePotion, 
        title="{\Gold Quaff which potion?}").push()
    return False

#========================================

def do_read(cmd):
    """
    Command "read". Attempts to cause the player to read a scroll by calling
    player.read(). Fails if no scrolls are present in player's inventory. If
    multiple scrolls are present, asks the player to select which one to read.

    Advances time iff successful.
    """
    if not player.tileIsBrightEnough(player.parentTile):
        transient("It is too dark here to read.")
        return False

    targets = []
    for obj in player.inventory:
        if isinstance(obj, sim_items.Scroll):
            targets.append(obj)

    if not targets:
        transient("You have no scrolls.")
        return False

    def __handleChooseScroll(target):
        cmdPoll.awaitCommand = False
        evtime = player.read(target)
        if evtime < 0:
            # nothing happened; get another command
            schedule.add(sim.Event('get command', schedule.time))
        else:
            schedule.add(sim.Event('get command', evtime + 1))

    interface.ObjectChooser(targets, __handleChooseScroll, 
        title="{\Gold Read which scroll?}").push()
    return False

#========================================

def do_throw(cmd):
    """
    Command "throw". Attempts to cause the player to throw an item by calling
    player.throw(). Throwable items include all unworn items in iventory, plus
    the currently wielded weapon, if any. Fails if no throwable items are
    present in player's inventory. If multiple throwable items are present, asks
    the player to select which one to throw. Asks the player for a target tile
    using interface.TargetTileInterface.

    Advances time iff successful.
    """
    targets = []
    for obj in player.inventory:
        if obj is player.wearSlots['weapon'] or \
                not (isinstance(obj, sim_items.Wearable) and obj.wearer):
            targets.append(obj)

    if not targets:
        transient("You have nothing at hand to throw.")
        return

    def __handleChooseItem(item):
        def __cancelChooseTile():
            # nothing happened; get another command
            cmdPoll.awaitCommand = False
            schedule.add(sim.Event('get command', schedule.time))

        def __handleChooseTile(tile):
            if tile is player.parentTile:
                transient("You can't throw things at yourself.")
                return
            cmdPoll.awaitCommand = False
            evtime = player.throw(item, tile)
            if evtime < 0:
                parole.debug('***** OH SHIT ******** NIGGA *******')
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))

        interface.TargetTileInterface(__handleChooseTile, 
            cancelCallback=__cancelChooseTile, visibleOnly=False,
            overlayPath=True,
            maxRange=sim.Trajectory.maxRangeOf(
                sim.Trajectory.velocityAtEnergy(player.throwEnergy(),
                    item.weight))
            ).push()
        

    verb = random.choice(['Throw', 'Fling', 'Chuck', 'Hurl', 'Launch',
        'Whip'])
    interface.ObjectChooser(targets, __handleChooseItem, 
        title="{\Gold %s what%s?}" % (verb, random.random()<0.25 and ' now' \
                or '')).push()
    return False

#========================================

def do_help(cmd):
    """
    Command "help". Displays the textual contents of main.helpText in an
    interface.ModalBox.

    Does not advance time.
    """
    interface.ModalBox(main.helpText, main.arial, lambda: 0).push()

#========================================

def do_messages(cmd):
    """
    Command "messages". Displays the last 200 transient messages, as stored in
    main.messageHistory, in an interface.ModalBox.

    Does not advance time.
    """
    txt = '{\Gold Recent Message History (most recent first):}\n\n' + \
            '\n'.join(main.messageHistory[:200])
    interface.ModalBox(txt, main.arial, lambda: 0).push()

#========================================

def do_known_items(cmd):
    """
    Command "known items". Displays a list of all identified item nouns,
    together with their unidentified nouns, in an interface.ModalBox.

    Does not advance time.
    """
    text = '{\Gold You recognize the following items:}\n'
    for itemClass in player.identifiedItemClasses:
        i = itemClass()
        text += "\n%s (%s)" % (i.noun(), i.noun(forceUnId=1))
    interface.ModalBox(text, main.arial, lambda: 0).push()

#========================================

def do_options(cmd):
    """
    Command "options". Pushes an instance of the interface.OptionsMenu interface
    for viewing and modifying game options (volume, fullscreen, etc.).

    Does not advance time.
    """
    interface.OptionsMenu().push()

#========================================
# Wizard mode commands. parole.conf.options.wizard must be True for these to
# have effect.
#========================================

def do_more_ambient(cmd):
    """
    Wizard mode command "more ambient". Increases the ambient light intensity of
    the current map by 0.05.

    Does not advance time.
    """
    if parole.conf.options.wizard:
        map = main.mapFrame.getMap()
        map.setAmbientLight((255,255,255), min(1., map.ambientIntensity + 0.05))
        map.update()

def do_less_ambient(cmd):
    """
    Wizard mode command "less ambient". Decreases the ambient light intensity of
    the current map by 0.05.

    Does not advance time.
    """
    if parole.conf.options.wizard:
        map = main.mapFrame.getMap()
        map.setAmbientLight((255,255,255), max(0., map.ambientIntensity - 0.05))
        map.update()

def do_toggle_fov(cmd):
    """
    Wizard mode command "toggle fov". Toggles whether the player's view is
    restricted to his Field of View, or unrestricted to the entire map.

    Does not advance time.
    """
    if parole.conf.options.wizard:
        mapFrame = main.mapFrame
        map = mapFrame.getMap()
        if not mapFrame.fovObj:
            mapFrame.bindVisibilityToFOV(player, player['perception'],
                remember=False, fovCondition=player.tileIsBrightEnough)
        else:
            mapFrame.bindVisibilityToFOV(None, 0)
        map.update()

def do_toggle_clipping(cmd):
    """
    Wizard mode command "toggle clipping". Toggles whether the player can walk
    through walls and other objects that normally block movement, by setting the
    Player.clipping attribute.

    Does not advance time.
    """
    if parole.conf.options.wizard:
        player.clipping = not player.clipping

def do_toggle_monster_reporting(cmd):
    """
    Wizard mode command "toggle monster reporting". Toggles whether monster
    statistics are logged when examining tiles containing monsters, by setting
    the player.monsterReporting attribute.

    Does not advance time.
    """
    if parole.conf.options.wizard:
        player.monsterReporting = not player.monsterReporting

def do_makelevel(cmd):
    """
    Wizard mode command "makelevel {town,1,2,3,...}". Generates a new level of
    the given depth (or the town) and teleports the player there. If a level of
    the given depth has already been generated, simply teleports the player to
    the existing level.

    Advances time minimally.
    """
    if cmd.endswith('town'):
        depth = 'town'
    else:
        depth = int(cmd[-1])

    main.fadeMusicOut(1000)
    if (depth not in main.levels) or (depth == 'town'):
        if depth == 'town':
            thelevel, ustairPos, dstairPos = town.makeNewTown()
        else:
            thelevel, ustairPos, dstairPos = dungeon.makeLevel(depth)
        main.addLevel(depth, thelevel, ustairPos, dstairPos)
        main.originalAmbientIntensity[thelevel] = thelevel.ambientIntensity
    else:
        thelevel, ustairPos, dstairPos = levels[depth]

    schedule.listen('new map', main.MapSender(thelevel, ustairPos))
    schedule.add(sim.Event('new map', schedule.time,
        "Your mighty wizard powers bring you to level %r!" % depth))
    return True

for depth in ['town'] + range(1, 7):
    globals()['do_makelevel_%s' % depth] = do_makelevel

def do_xp(cmd):
    """
    Wizard mode command "xp". Gives the player 10 experience points.

    Advances time minimally.
    """
    schedule.add(sim.Event('increase stat', schedule.time,
        (player, 'xp', 10),
        dests=[player]))
    schedule.add(sim.Event('get command', schedule.time+1))
    return True

def do_place_thing(cmd):
    """
    Wizard mode command "place thing". Asks the player for the location of a
    thing script (without the .py suffix; relative to /scripts/), and if valid,
    asks the player to choose a tile at which to place a new instance of that
    thing. Prevents the player from placing a thing that blocks movement at a
    tile already containing another thing that blocks movement.

    Does not advance time.
    """
    def onEnterThing(thingName):
        thingName = thingName.strip()
        if not thingName:
            return
        thingParts = thingName.split(':')
        thingBase = thingParts.pop(0)
        try:
            thing = dungeon.getThing(thingBase)
        except dungeon.ThingNotFound, e:
            parole.error('[wizard] Unknown thing: %r', thingName)
            return
        for affixName in thingParts:
            try:
                affix = dungeon.getThing(affixName)
            except dungeon.ThingNotFound, e:
                parole.error('[wizard] Unknown affix: %r', affixName)
                return
            try:
                thing.addAffix(affix)
            except Exception, e:
                parole.error('[wizard] Failed to apply affix %r to %r: %s',
                        affix, thing, e)
                return

        # get destination tile for thing to place
        def onChooseTile(tile):
            parole.info('[wizard] Placing %r at %s',
                    thing, tile)
            if thing.blocksMove and tile.hasMoveBlocker():
                parole.error('[wizard] Destination tile is blocked!')
                interface.ModalBox('[wizard] Distination tile is blocked!',
                        main.arial, lambda: None).push()
            else:
                tile.add(thing)
                transient('Your might wizard powers summon %s into '
                        'existence.' % thing, tile)
        def onCancelTile():
            parole.info('[wizard] Canceled placing %r.', thing)
        interface.TileChooser('Place %s here.' % thing, onChooseTile,
                onCancelTile).push()

    interface.TextLineInput('Thing to place (e.g., potions/healing): ',
                main.arial, onEnterThing, width=600).push()
